/*
MIT License

Copyright (c) 2021 МГТУ им. Н.Э. Баумана, кафедра ИУ-6, Михаил Фетисов,

https://bmstu.codes/lsx/simodo/loom
*/

#include "simodo/interpret/builtins/modules/AstFormationModule.h"
#include "simodo/variable/VariableSetWrapper.h"
#include "simodo/parser/fuze/BaseOperationCode.h"

#include "simodo/bormental/DrBormental.h"

// #include "simodo/interpret/script/ScriptOperationCode.h"

#include <cassert>

#define LOGGER_no

namespace simodo::interpret::builtins
{
    namespace
    {
    #ifdef LOGGER
        AReporter *  logger_  = nullptr;
    #endif

        variable::Value addNode(variable::Module_interface * host, const variable::VariableSetWrapper & args)
        {
    #ifdef LOGGER
            if (logger_ != nullptr)
                logger_->reportInformation(u"/tAststb::addNode");
    #endif
            assert(host != nullptr);
            AstFormationModule & stb = *(static_cast<AstFormationModule *>(host));

            const variable::Variable & host_value  = args[0].origin();
            const variable::Variable & op_value    = args[1].origin();
            const variable::Variable & index_value = args[2].origin();

            if (index_value.type() == variable::ValueType::String)
            {
                std::u16string value { get<std::u16string>(index_value.variant()) };
                inout::Token   token { inout::LexemeType::Id, value, args[2].origin().location() };

                stb().addNode(get<std::u16string>(host_value.variant()),
                              static_cast<ast::OperationCode>(get<int64_t>(op_value.variant())),
                              token,
                              token
                              );
            }
            else if (index_value.type() == variable::ValueType::Int)
            {
                int64_t index = get<int64_t>(index_value.variant());

                assert((&stb.current_pattern()) != nullptr);
                assert((&stb.current_production()) != nullptr);

                if (index < 0 || index >= static_cast<int64_t>(stb.current_pattern().size()))
                    throw bormental::DrBormental("addNode", "Выход индекса за пределы массива");

                stb().addNode(get<std::u16string>(host_value.variant()),
                              static_cast<ast::OperationCode>(get<int64_t>(op_value.variant())),
                              stb.current_pattern()[static_cast<size_t>(index)],
                              stb.current_production()
                              );
            }
            else
                throw bormental::DrBormental("addNode", "Недопустимый тип индекса");

            return {};
        }

        variable::Value addNode_StepInto(variable::Module_interface * host, const variable::VariableSetWrapper & args)
        {
    #ifdef LOGGER
            if (logger_ != nullptr)
                logger_->reportInformation(u"/tAststb::addNode_StepInto");
    #endif
            assert(host != nullptr);
            AstFormationModule & stb = *(static_cast<AstFormationModule *>(host));

            const variable::Variable & host_value  = args[0].origin();
            const variable::Variable & op_value    = args[1].origin();
            const variable::Variable & index_value = args[2].origin();

            if (index_value.type() == variable::ValueType::String)
            {
                std::u16string value { get<std::u16string>(index_value.variant()) };
                inout::Token   token { inout::LexemeType::Id, value, args[2].origin().location() };

                stb().addNode_StepInto(get<std::u16string>(host_value.variant()),
                              static_cast<ast::OperationCode>(get<int64_t>(op_value.variant())),
                              token,
                              token
                              );
            }
            else if (index_value.type() == variable::ValueType::Int)
            {
                int64_t index = get<int64_t>(index_value.variant());

                assert((&stb.current_pattern()) != nullptr);
                assert((&stb.current_production()) != nullptr);

                if (index < 0 || index >= static_cast<int64_t>(stb.current_pattern().size()))
                    throw bormental::DrBormental("addNode_StepInto", "Выход индекса за пределы массива");

                stb().addNode_StepInto(get<std::u16string>(host_value.variant()),
                              static_cast<ast::OperationCode>(get<int64_t>(op_value.variant())),
                              stb.current_pattern()[static_cast<size_t>(index)],
                              stb.current_production()
                              );
            }
            else
                throw bormental::DrBormental("addNode_StepInto", "Недопустимый тип индекса");

            return {};
        }

        variable::Value goParent(variable::Module_interface * host, const variable::VariableSetWrapper & )
        {
    #ifdef LOGGER
            if (logger_ != nullptr)
                logger_->reportInformation(u"/tAststb::goParent");
    #endif
            assert(host != nullptr);
            AstFormationModule & stb = *(static_cast<AstFormationModule *>(host));

            bool ok = stb().goParent();

            if (!ok)
                throw bormental::DrBormental("goParent", "Недопустимое использование goParent");

            return {};
        }

    //     Variable setStreamNo(variable::Module_interface * host, const variable::VariableSetWrapper & args)
    //     {
    // #ifdef LOGGER
    //         if (logger_ != nullptr)
    //             logger_->reportInformation(u"/tAststb::setStreamNo");
    // #endif
    //         assert(host != nullptr);
    //         AstFormationModule & stb = *(static_cast<AstFormationModule *>(host));

    //         int64_t no = get<int64_t>(args[0].origin().variant());

    //         if (no < 0 || no > UINT16_MAX)
    //             throw Exception("setStreamNo", "Выход номера потока за лимиты");

    //         stb.setCurrentStreamNo(no);

    //         return error_variable();
    //     }

        // GrammarManagement_null gm_dummy;

        static variable::VariableSet_t hosts;
    }

    using namespace simodo::variable;

    AstFormationModule::AstFormationModule()
        : _hosts(hosts)
    {
    }

    AstFormationModule::AstFormationModule(variable::VariableSet_t & hosts)
        : _hosts(hosts)
    {
    }

    variable::Object AstFormationModule::instantiate(std::shared_ptr<variable::Module_interface> module_host)
    {
        variable::VariableSet_t ret {{
            {u"addNode", {ValueType::Function, variable::Object {{
                {u"@", ExternalFunction {{module_host}, addNode}},
                {u"", ValueType::Null},
                {u"host", ValueType::String},
                {u"op", ValueType::Int},
                // Неопределённый тип для индекса предполагает тип параметра 'any'
                {u"index", ValueType::Null},
            }}}},
            {u"addNode_StepInto", {ValueType::Function, variable::Object {{
                {u"@", ExternalFunction {{module_host}, addNode_StepInto}},
                {u"", ValueType::Null},
                {u"host", ValueType::String},
                {u"op", ValueType::Int},
                // Неопределённый тип для индекса предполагает тип параметра 'any'
                {u"index", ValueType::Null},
            }}}},
            {u"goParent", {ValueType::Function, variable::Object {{
                {u"@", ExternalFunction {{module_host}, goParent}},
                {u"", ValueType::Null},
                // {u"host", ValueType::String},
            }}}},
            // {u"setStream", ValueType::Function, variable::Object {
            //     {u"@", ValueType::ExtFunction, ExternalFunction {{}, setStreamNo}},
            //     {u"", ValueType::Error, {}},
            //     {u"no", ValueType::Int, {}},
            // }},
            // {u"Stream", ValueType::Enum, variable::Object {
            //     {u"Default", ValueType::Int, static_cast<int64_t>(DEFAULT_STREAM_NO)},
            //     {u"Equation", ValueType::Int, static_cast<int64_t>(EQUATION_STREAM_NO)},
            //     {u"Diff", ValueType::Int, static_cast<int64_t>(DIFF_STREAM_NO)},
            // }},
            {u"op", variable::Object {{
                {u"None", static_cast<int64_t>(parser::BaseOperationCode::None)},
                {u"PushConstant", static_cast<int64_t>(parser::BaseOperationCode::PushConstant)},
                {u"PushVariable", static_cast<int64_t>(parser::BaseOperationCode::PushVariable)},
                {u"ObjectElement", static_cast<int64_t>(parser::BaseOperationCode::ObjectElement)},
                {u"FunctionCall", static_cast<int64_t>(parser::BaseOperationCode::FunctionCall)},
                {u"ProcedureCheck", static_cast<int64_t>(parser::BaseOperationCode::ProcedureCheck)},
                {u"Print", static_cast<int64_t>(parser::BaseOperationCode::Print)},
                {u"Block", static_cast<int64_t>(parser::BaseOperationCode::Block)},
                {u"Pop", static_cast<int64_t>(parser::BaseOperationCode::Pop)},

            }}},
            {u"host", variable::Object {{
                {u"SBL", u""},
                {u"fuze", u"fuze"},
            }}},
        }};

        ret.insert(ret.end(), _hosts.begin(), _hosts.end());

        return ret;
    }

    // variable::ModuleFactory_interface* AstFormationModule::factory()
    // {
    //     // std::cout << "factory" << std::endl;

    //     return nullptr;
    // }
}